﻿
using System.Diagnostics;

using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.CompilerServices.SymbolWriter;

namespace il2lua.TypeSystem;

public record class Data(int Id, string Name);


public class ILBlock
{
    public ILBlock()
    {
        var data = new Data(1, "sss");
        data.Id = 5;
        Instructions = new List<IInstruction>();
    }
    public IList<IInstruction> Instructions { get; }
}


public class ILModule : ILNameSpace, IModule
{
    private readonly ISymbolReader _symbol;
    private readonly IDictionary<string, IRole> _roles;
    private readonly IList<IMethod> _methods;
    private readonly IList<IType> _types;
    public IList<IType> Types => _types;
    public IList<IMethod> Methods => _methods;
    public ILModule(ModuleDefinition module, ISymbolReader symbol)
        : base(null, null)
    {
        _symbol = symbol;
        _roles = new Dictionary<string, IRole>();
        _types = new List<IType>();
        _methods = new List<IMethod>();
        Kind = ObjectKind.Void;
        foreach (var type in module.Types)
        {
            if (!type.IsExcluded() && !_roles.ContainsKey(type.FullName))
            {
                CreateOrGetType(type);
            }
        }
    }

    private IField CreateOrGetField(IType type, FieldDefinition fieldDef)
    {
        if (_roles.TryGetValue(fieldDef.FullName, out var field))
        {
            Debug.Assert(field is IField);
            return (IField)field;
        }
        var name = fieldDef.GetIdentifier();
        IField iField;
        if (fieldDef.IsStatic)
            iField = new ILField(name, type, FieldKind.Static, fieldDef.FieldType.ToStackType());
        else
            iField = new ILField(name, type, FieldKind.Instance, fieldDef.FieldType.ToStackType());
        _roles[fieldDef.FullName] = iField;
        return iField;
    }

    private IParam CreateOrGetParam(IMethod method, string fullname, ParameterDefinition paramDef)
    {
        var key = $"{fullname}.__p{paramDef.Index}__";
        if (_roles.TryGetValue(key, out var param))
        {
            Debug.Assert(param is IParam);
            return (IParam)method;
        }
        string name;
        if (paramDef.Index == -1)
            name = "self";
        else if (paramDef.HasCustomAttribute<ParamArrayAttribute>())
            name = "...";
        else
            name = ToName(paramDef.Name);
        var iParam = new ILParam(paramDef.Index, name, method, paramDef.ParameterType.ToStackType());
        _roles[key] = iParam;
        return iParam;
    }

    private IVariable CreateOrGetVariable(IMethod method, string fullname, VariableDefinition variableDef, MethodDebugInformation? information)
    {
        var key = $"{fullname}.__v{variableDef.Index}__";
        if (_roles.TryGetValue(key, out var variable))
        {
            Debug.Assert(variable is IVariable);
            return (IVariable)variable;
        }
        string name;
        if (information != null && information.TryGetName(variableDef, out name))
            name = ToName(name);
        else
            name = $"k__var{variableDef.Index}_backing";
        var iVariable = new ILVariable(variableDef.Index, name, method, variableDef.VariableType.ToStackType());
        _roles[key] = iVariable;
        return iVariable;
    }
    private IMethod CreateOrGetMethod(IType type, MethodDefinition methodDef)
    {

        if (_roles.TryGetValue(methodDef.FullName, out var method))
        {
            Debug.Assert(method is IMethod);
            return (IMethod)method;
        }
        MethodDebugInformation? information = null;
        if (_symbol != null)
            information = _symbol.Read(methodDef);

        var name = methodDef.GetIdentifier();
        name = type.RenameMethod(name);
        MethodKind kind;
        if (methodDef.IsStatic)
            kind = MethodKind.Static;
        else
            kind = MethodKind.Instance;
        if (methodDef.IsConstructor)
        {
            kind |= MethodKind.Ctor;
            name = "__ctor__";
        }
        var iMethod = new ILMethod(kind, name, type, methodDef.ReturnType.ToStackType());
        foreach (var param in methodDef.Parameters)
            iMethod.Params.Add(CreateOrGetParam(iMethod, methodDef.FullName, param));
        if (methodDef.HasBody)
        {
            foreach (var variable in methodDef.Body.Variables)
                iMethod.Variables.Add(CreateOrGetVariable(iMethod, methodDef.FullName, variable, information));
            if (!methodDef.IsStatic)
                iMethod.SelfParam = CreateOrGetParam(iMethod, methodDef.FullName, methodDef.Body.ThisParameter);
            iMethod.RawInstructions = methodDef.Body.Instructions;
            var block = new ILBlock();
        }
        _roles[methodDef.FullName] = iMethod;
        _methods.Add(iMethod);
        return iMethod;
    }

    private IType CreateOrGetType(TypeDefinition typeDef)
    {
        if (_roles.TryGetValue(typeDef.FullName, out var type))
        {
            Debug.Assert(type is IType);
            return (IType)type;
        }
        var parent = GetNameSpace(typeDef);
        var iType = new ILType(typeDef.GetIdentifier(), parent);
        AnalyseType(typeDef, iType);
        foreach (var field in typeDef.Fields)
            iType.Fields.Add(CreateOrGetField(iType, field));
        foreach (var method in typeDef.Methods)
            iType.Methods.Add(CreateOrGetMethod(iType, method));
        return iType;
    }

    private void AnalyseType(TypeDefinition typeDef, IType type)
    {
        _roles[typeDef.FullName] = type;
        _types.Add(type);
        foreach (var nestedType in typeDef.NestedTypes)
            CreateOrGetType(nestedType);
    }

    private INameSpace GetNameSpace(TypeDefinition typeDef)
    {
        if (typeDef.DeclaringType != null)
            return CreateOrGetType(typeDef.DeclaringType);
        else
            return this.GetNameSpace(typeDef.Namespace.Split('.', StringSplitOptions.RemoveEmptyEntries));
    }

    public ObjectKind Kind { get; }
}
